09. Testing Local Data Source

You just created unit tests for your TasksDao. In this step, you'll create integration tests for your TasksLocalDataSource. TasksLocalDatasource is a class that takes the information returned by the DAO and converts it to a format that is expected by your repository class (for example, it wraps returned values with Success or Error states). You'll be writing an integration test because you'll test both the real TasksLocalDatasource code and the real DAO code.

Step 1: Create an integration test for TasksLocalDataSource

The steps for creating tests for your TasksLocalDataSourceTest are very similar to creating the DAO tests.

  1. Open your app's TasksLocalDataSource class.
  2. Right-click on the TasksLocalDataSource class name and select Generate, then Test.
  3. Follow the prompts to create TasksLocalDataSourceTest in the androidTest source set.
  4. Copy the following code:

TasksLocalDataSourceTest.kt

@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
@MediumTest
class TasksLocalDataSourceTest {

    // Executes each task synchronously using Architecture Components.
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

}

Notice that the only real difference between this and the DAO testing code is that the TasksLocalDataSource can be considered a medium "integration" test (as seen by the @MediumTest annotation), because the TasksLocalDataSourceTest will test both the code in TasksLocalDataSource and how it integrates with the DAO code.

  1. In TasksLocalDataSourceTest, create a lateinit field for the two components you're testing–- TasksLocalDataSource and your database:

TasksLocalDataSourceTest.kt

private lateinit var localDataSource: TasksLocalDataSource
private lateinit var database: ToDoDatabase
  1. Make a @Before method for initializing your database and datasource.
  2. Create your database in the same way you did for your DAO test, using the inMemoryDatabaseBuilder and the ApplicationProvider.getApplicationContext() method
  3. Add allowMainThreadQueries. Normally Room doesn't allow database queries to be run on the main thread. Calling allowMainThreadQueries turns off this check. Don't do this in production code!
  4. Instantiate the TasksLocalDataSource, using your database and Dispatchers.Main. This will run your queries on the main thread (this is allowed because of allowMainThreadQueries).

TasksLocalDataSourceTest.kt

@Before
fun setup() {
    // Using an in-memory database for testing, because it doesn't survive killing the process.
    database = Room.inMemoryDatabaseBuilder(
        ApplicationProvider.getApplicationContext(),
        ToDoDatabase::class.java
    )
        .allowMainThreadQueries()
        .build()

    localDataSource =
        TasksLocalDataSource(
            database.taskDao(),
            Dispatchers.Main
        )
}
  1. Make an @After method for cleaning up your database using database.close().

The complete code should look like this:

TasksLocalDataSourceTest.kt

@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
@MediumTest
class TasksLocalDataSourceTest {

    private lateinit var localDataSource: TasksLocalDataSource
    private lateinit var database: ToDoDatabase


    // Executes each task synchronously using Architecture Components.
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setup() {
        // Using an in-memory database for testing, because it doesn't survive killing the process.
        database = Room.inMemoryDatabaseBuilder(
            ApplicationProvider.getApplicationContext(),
            ToDoDatabase::class.java
        )
            .allowMainThreadQueries()
            .build()

        localDataSource =
            TasksLocalDataSource(
                database.taskDao(),
                Dispatchers.Main
            )
    }

    @After
    fun cleanUp() {
        database.close()
    }

}

Step 2: Write your first TasksLocalDataSourceTest

Let's copy and run an example test first.

  1. Copy these import statements:
import com.example.android.architecture.blueprints.todoapp.data.source.TasksDataSource
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.Task
import com.example.android.architecture.blueprints.todoapp.data.succeeded
import kotlinx.coroutines.runBlocking
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert.assertThat
import org.junit.Test
  1. Copy this test:

TasksLocalDataSourceTest.kt

// runBlocking is used here because of https://github.com/Kotlin/kotlinx.coroutines/issues/1204
// TODO: Replace with runBlockingTest once issue is resolved
@Test
fun saveTask_retrievesTask() = runBlocking {
    // GIVEN - A new task saved in the database.
    val newTask = Task("title", "description", false)
    localDataSource.saveTask(newTask)

    // WHEN  - Task retrieved by ID.
    val result = localDataSource.getTask(newTask.id)

    // THEN - Same task is returned.
    assertThat(result.succeeded, `is`(true))
    result as Success
    assertThat(result.data.title, `is`("title"))
    assertThat(result.data.description, `is`("description"))
    assertThat(result.data.isCompleted, `is`(false))
}

This is very similar to your DAO test. Like the DAO test, this test:

  • Creates a task and inserts it into the database.
  • Retrieves the task using its id.
  • Asserts that that task was retrieved, and that all its properties match the inserted task.

The only real difference from the analogous DAO test is that the local data source returns an instance of the sealed Result class, which is the format the repository expects. For example, this line here casts the result as a Success:

TasksLocalDataSourceTest.kt

assertThat(result.succeeded, `is`(true))
result as Success
  1. Run your test!

Step 3: Write your own local data source test

Now it's your turn.

  1. Copy the following code:

TasksLocalDataSourceTest.kt

@Test
fun completeTask_retrievedTaskIsComplete(){
    // 1. Save a new active task in the local data source.

    // 2. Mark it as complete.

    // 3. Check that the task can be retrieved from the local data source and is complete.

}
  1. Finish the code, referring to the saveTask_retrievesTask test you added previously, as needed.
  2. Run your test and confirm it passes!

The completed test is in the end_codelab_3 branch of the repository here, so that you can compare.